
package com.kweative.lingua.server;

import com.kweative.lingua.server.annotations.MethodInfo;
import com.kweative.lingua.server.controllers.InfoController;
import com.kweative.lingua.server.exceptions.NotFoundException;
import com.kweative.lingua.server.session.Session;
import com.kweative.lingua.server.structures.DataStore;
import com.kweative.type.Node;
import com.kweative.type.Node.Type;
import com.kweative.util.ReflectUtil;
import com.kweative.util.StringUtil;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author henrik
 */
abstract public class Application extends Object implements Runnable {
    private String name = "";
    private String namespace = "";
    private File homedir = null;
    private LinkedList<String> domains = new LinkedList<String>();
    private HashMap<String,Controller> controllers = new HashMap<String, Controller>();
    private DataStore<String,Object> datastore = null;
    public int timeout = 1800;
    private final Thread cleanupService;
    private final HashMap<String,Session> sessions = new HashMap<String,Session>();

    protected Application(String name,String namespace) {
        this.name = name;
        this.namespace = namespace;
        InfoController infoCtrl = new InfoController();
        addController(infoCtrl);

        //Init cleanup
        cleanupService = new Thread() {
            @Override
            public void run() {
                try {
                    sleep(1000 * 60 * 10);
                    synchronized(sessions) {
                        for(Session s:sessions.values()) {
                            if (s.getAge() > timeout) {
                                sessions.remove(s.getSID());
                            }
                        }
                    }

                } catch (InterruptedException ex) {
                    Logger.getLogger(Application.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        };
        cleanupService.setDaemon(true);
    }
    
    
    public void addController(Controller controller) {
        controller.setApp(this);
        this.controllers.put(controller.getName(),controller);
    }

    public DataStore<String, Object> getDatastore() {
        return datastore;
    }

    public LinkedList<String> domains() {
        return domains;
    }
    public Application addDomain(String domain) {
        domains.add(domain);
        return this;
    }

    public File getHomedir() {
        return homedir;
    }

    public void setHomedir(File homedir) {
        this.homedir = homedir;
    }
    

    public void setName(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
    public Controller<Application> getController(String className) throws NotFoundException {
        if (controllers.containsKey(className)) {
            return controllers.get(className);
        } else {
            throw new NotFoundException("Controller not found:" + className);
        }
    }
    public Class<Controller<Application>>[] getControllers() throws ClassNotFoundException {
        return ReflectUtil.getClasses(this.namespace,Controller.class,this);
    }
    public void run() {
        String oldHomeDir = System.getProperty("user.dir");
        //Attempt to change homedir
        System.setProperty("user.dir",getHomedir().getAbsolutePath());
        try {
            Runtime.getRuntime().exec(String.format("cd \"%s\"", getHomedir().getAbsolutePath()));
        } catch (IOException ex) {
            Logger.getLogger(Application.class.getName()).log(Level.SEVERE, null, ex);
        }
        //Run application load
        this.onApplicationLoad();

        //Attempt to change homedir
        System.setProperty("user.dir",oldHomeDir);
        try {
            Runtime.getRuntime().exec(String.format("cd \"%s\"", oldHomeDir));
        } catch (IOException ex) {
            Logger.getLogger(Application.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public Node getDocumentation() {
        Node root = new Node("application", Type.COMPLEX);
        root.setAttribute("name", name);

        Node ctrlsNode = new Node("controllers",Type.ARRAY);
        for(Controller<Application> ctrl:controllers.values()) {
            Node ctrlNode = new Node("controller",Type.COMPLEX);
            ctrlNode.setAttribute("name",ctrl.getName());
            Method[] methods = ctrl.getMethods();
            Node methodsNode = new Node("methods",Type.ARRAY);
            for(Method method:methods) {
                Node methodNode = new Node("method",Type.COMPLEX);
                methodNode.setAttribute("name",method.getName());
                Class<?>[] parms = method.getParameterTypes();
                MethodInfo info = method.getAnnotation(MethodInfo.class);
                String[] parmNames = new String[0];
                if (info != null) {
                    parmNames = info.parms();
                } 
                methodNode.setAttribute("returns",method.getReturnType().getSimpleName());
                Node parmsNode = new Node("parms",Type.ARRAY);
                for(int i = 0; i < parms.length;i++) {
                    Node parmNode = new Node("parm",Type.COMPLEX);
                    parmNode.setAttribute("type",parms[i].getSimpleName());
                    if (parmNames.length > i) {
                        parmNode.setAttribute("name",parmNames[i]);
                    }
                    parmsNode.addChild(parmNode);
                }
                if (parmsNode.hasChildren())
                    methodNode.addChild(parmsNode);
                methodsNode.addChild(methodNode);
            }
            ctrlNode.addChild(methodsNode);
            ctrlsNode.addChild(ctrlNode);
        }
        root.addChild(ctrlsNode);
        return root;        
    }
    public void onApplicationLoad() {
        datastore = new DataStore<String, Object>(this.getName() + "_datastore");
        cleanupService.start();
    };
    public void onApplicationUnload() {
        cleanupService.interrupt();
    };

    /* Sessions */
    
    public Session newSession() {
        synchronized(sessions) {
            int i = 0;
            String SID;
            while(true) {
                SID = StringUtil.GUID(i);
                if (!hasSession(SID)) {
                    break;
                }
                i++;
            }
            Session s = new Session(SID);
            sessions.put(SID, s);
            return s;
        }
    }
    public boolean hasSession(String SID) {
        synchronized(sessions) {
            return sessions.containsKey(SID);
        }
    }
    public Session getSession(String SID) {
        synchronized(sessions) {
            Session s = sessions.get(SID);
            if (s == null) return null;
            s.touch();
            return s;
        }
    }
}
